﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using NUnit.Framework;
using System.Collections.Generic;

namespace Legend.Web.QueryString.Tests
{
    [TestFixture]
    public class DataContractMemberAccessorTests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public virtual void Constructor_throws_when_type_is_null()
        {
            var accessor = new DataContractMemberAccessor(null);
        }

        [Test]
        public virtual void GetContractMembers_gets_all_tagged_members()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));
            var result = accessor.GetSerializableMembers();
            var resultAsDictionary = result.ToDictionary(x => x.Name, x => x);

            Assert.IsTrue(resultAsDictionary.ContainsKey("Foo"));
            Assert.IsTrue(resultAsDictionary.ContainsKey("bar"));
        }

        [Test]
        public virtual void GetContractMembers_does_not_get_non_tagged_members()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));
            var result = accessor.GetSerializableMembers();
            
            Assert.AreEqual(2, result.Count());
        }

        [Test]
        public virtual void GetContractMembers_gets_specified_type_converter()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));
            var result = accessor.GetSerializableMembers();

            Assert.AreEqual(typeof (CustomBoolConverter),
                            result.Where(x => x.Name == "Foo").Single().Converter.GetType());
        }

        [Test]
        public virtual void GetContractMembers_gets_correct_ContractMemberType()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));
            var result = accessor.GetSerializableMembers().ToDictionary(x => x.Name, x => x);

            Assert.AreEqual(typeof(bool), result["Foo"].ContractMemberType);
            Assert.AreEqual(typeof(DateTime), result["bar"].ContractMemberType);
        }

        [Test]
        public virtual void GetContractMembers_gets_default_convereter_when_not_specified()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));
            var result = accessor.GetSerializableMembers();

            Assert.AreEqual(typeof(DateTimeConverter),
                            result.Where(x => x.Name == "bar").Single().Converter.GetType());
        }

        [Test]
        public virtual void GetContractMembers_SetValue_sets_value()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));
            var result = accessor.GetSerializableMembers().ToDictionary(x => x.Name, x => x);

            var query = new FooQuery();

            result["Foo"].SetValue(query, true);
            result["bar"].SetValue(query, new DateTime(1977, 4, 5));

            Assert.IsTrue(query.Foo);
            Assert.AreEqual(new DateTime(1977, 4, 5), query.Bar);
        }

        [Test]
        public virtual void GetContractMembers_GetValue_gets_value()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));
            var result = accessor.GetSerializableMembers().ToDictionary(x => x.Name, x => x);

            var query = new FooQuery();
            query.Foo = true;
            query.Bar = new DateTime(1977, 4, 5);

            Assert.AreEqual(true, result["Foo"].GetValue(query));
            Assert.AreEqual(new DateTime(1977, 4, 5), result["bar"].GetValue(query));
        }


        [Test, ExpectedException(typeof(InvalidOperationException))]
        public virtual void Constructor_throws_when_type_is_not_tagged_with_DataContractAttribute()
        {
            var accessor = new DataContractMemberAccessor(typeof(NonAttributedQuery));
        }

        [Test]
        public virtual void GetContractMembers_gets_members_with_specified_order_before_other_members()
        {
            var accessor = new DataContractMemberAccessor(typeof(OrderedQuery));

            var result = accessor.GetSerializableMembers().ToArray();

            Assert.AreEqual("A", result[0].Name);
            Assert.AreEqual("B", result[1].Name);
            Assert.AreEqual("D", result[2].Name);
            Assert.AreEqual("C", result[3].Name);
        }

        [Test]
        public virtual void GetContractMembers_gets_members_orders_set()
        {
            var accessor = new DataContractMemberAccessor(typeof(OrderedQuery));

            var result = accessor.GetSerializableMembers().ToDictionary(x => x.Name, x => x);
            Assert.IsNull(result["B"].Order);
            Assert.AreEqual(0, result["D"].Order);
            Assert.AreEqual(1, result["C"].Order);
        }

        [Test]
        public virtual void GetContractMembers_can_be_accessed_by_member_name()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));

            var result = accessor.GetSerializableMembers();
            Assert.IsNotNull(result["Foo"]);
        }


        [Test, ExpectedException(typeof(KeyNotFoundException))]
        public virtual void GetContractMembers_collection_throws_KeyNotFoundException_if_member_does_not_exist()
        {
            var accessor = new DataContractMemberAccessor(typeof(FooQuery));

            var result = accessor.GetSerializableMembers();
            var nonExisting = result["this does not exist"];
        }


        [DataContract]
        private class FooQuery
        {
            [DataMember]
            [TypeConverter(typeof(CustomBoolConverter))]
            public bool Foo { get; set; }

            [DataMember(Name="bar")]
            public DateTime Bar;

            public string NonContractMember;
        }

        [DataContract]
        private class OrderedQuery
        {
            [DataMember]
            public string B;
            [DataMember]
            public string A;
            [DataMember(Order=1)]
            public string C;
            [DataMember(Order=0)]
            public string D;
        }

        private class NonAttributedQuery
        {
            [DataMember]
            public int Foo { get; set; }
        }

        private class CustomBoolConverter
            : TypeConverterBase<bool, string>
        {
            protected override bool ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, string value)
            {
                return string.Equals("yes", value, StringComparison.OrdinalIgnoreCase);
            }

            protected override string ConvertTo(ITypeDescriptorContext context, CultureInfo culture, bool value)
            {
                return value ? "yes" : "no";
            }
        }
    }
}
